Caution
Version corrigée

Cette version comporte des indications pour les réponses aux exercices.

PreReq

1. Je sais programmer en Java. 2. J’ai conscience qu’il faut réfléchir avant de se lancer dans le codage. 3. Je maîtrise les concepts objet de base (héritage, polymorphisme, …​). 4. J’ai compris ce qu’est un patron et j’ai grand soif d’en apprendre d’autres que Strategy et Singleton

ObjTD

Aborder le patron fabrique.

Durée

1 TD et 2 TP de 1,5h (sur 2 semaines).

1. Rappel du cours

Tip N’hésitez pas à (re)lire régulièrement le Support de Cours.

2. La pizzeria O’Reilly

Vous êtes embauché dans une pizzeria pour faire …​ de l’informatique!

Soit le code de départ suivant :

/**
 * @author bruel (from O'Reilly Head-First series)
 *
 */
public class Pizzeria {

	Pizza commanderPizza(String type) {

		Pizza pizza;

		if (type.equals("fromage")) {
			pizza = new PizzaFromage();
		} else if (type.equals("grecque") {
			pizza = new PizzaGrecque();
		} else if (type.equals("poivrons") {
			pizza = new PizzaPoivrons();
		}

		pizza.preparer();
		pizza.cuire();
		pizza.couper();
		pizza.emballer();

		return pizza;
		}

	}
}
Warning
QUESTION
  1. Identifiez ce qui varie dans ce code (si la pression du marché fait ajouter des pizzas à la carte ou si une pizza n’a plus de succès et doit disparaître, etc.).

  2. Isolez dans une classe SimpleFabriqueDePizzas ce code.

  3. Réalisez le diagramme de classe obtenu.

  4. Quel est l’avantage de procéder ainsi ? Ne transfère-t’on pas simplement le problème à un autre objet ?

Tip Bien sûr vous héritez de cet horrible "if then else" et dans votre implémentation en TP vous remplacerez ce code avantageusement par un "switch case" et utilisez une enum comme vu en cours.
Important
Solution
  1. Le if à remplacer par un pizza = fabrique.creerPizza(type); et on ajoute le code suivant dans la classe :

    	SimpleFabriqueDePizzas fabrique;
    
    	public Pizzeria(SimpleFabriqueDePizzas fabrique) {
    		this.fabrique = fabrique;
    	}
    Note Faire remarquer que la pizza n’est plus la propriété de la pizzeria, mais de la fabrique!
  2. Code de SimpleFabriqueDePizzas

    /**
     * @author bruel
     * @depend - new - Pizza
     */
    public class SimpleFabriqueDePizzas {
    
    	public Pizza creerPizza(String type) {
    		Pizza pizza;
    
    		if (type.equals("fromage")) {
    			pizza = new PizzaFromage();
    		} else if (type.equals("saumon")) {
    			pizza = new PizzaSaumon();
    		} else if (type.equals("royale")) {
    			pizza = new PizzaRoyale();
    		} else {
    			pizza = new PizzaVegetarienne();
    		}
    
    		return pizza;
    	}
    
    }
  3. Diagramme de classe :

    Pizzeria2
  4. En encapsulant la création des pizzas dans une seule classe, nous n’avons plus qu’un seul endroit auquel apporter des modifications quand l’implémentation change.

3. On y est presque…​

Nous sommes arrivé à une situation propre, qui s’apparente à un patron de conception. Mais avant d’en arriver à la définition du patron lui-même, nous allons améliorer un peu les choses.

3.1. Succès des pizzerias O’Reilly : les franchises

Plusieurs villes veulent ouvrir des pizzerias comme la votre. Votre patron, très content de vos programmes souhaite imposer à toutes les futures pizzerias d’utiliser vos codes.

Le problème : les pizzas au fromage de Starsbourg sont différentes des pizzas aux fromages de Corse!

Warning
QUESTION

Proposez une solution où SimpleFabriqueDePizzas serait une classe abstraite.

Important
Solution

Simplement ajouter abstract, créer plusieurs sous-classes et avoir une utilisation du style :

FabriqueDePizzasBrest fabriqueBrest = new FabriqueDePizzasBrest();
Pizzeria boutiqueBrest = new Pizzeria(fabriqueBrest);
boutiqueBrest.commander("Végétarienne");
...
FabriqueDePizzasStrasbourg fabriqueStrasbourg = new FabriqueDePizzasStrasbourg();
Pizzeria boutiqueStrasbourg = new Pizzeria(fabriqueStrasbourg);
boutiqueStrasbourg.commander("Végétarienne");

3.2. La dérive : chacun travaille comme il l’entend!

Les pizzerias utilisent bien vos fabriques mais ont changé les procédures : certains ne coupent pas les pizzas, changent les temps de cuissons, et les pizzerias O’Reilly perdent leur identité. Il nous faut restructurer les pizzerias.

Un consultant italien payé fort cher (heureusement en pizzas!) propose de revenir à une structure suivante :

public abstract class Pizzeria {
	public Pizza commanderPizza(String type) {
		Pizza pizza;

		pizza = creerPizza(type);
		pizza.preparer();
		pizza.cuire();
		pizza.couper();
		pizza.emballer();

		return pizza;
	}

	abstract creerPizza(String type);
}
Warning
QUESTION

Quelles sont les différences avec notre conception actuelle?

Important
Solution
  • Pizzeria est maintenant abstraite (vous allez voir pourquoi ci-dessous).

  • Maintenant, creerPizza() est de nouveau un appel à une méthode de Pizzeria et non à un objet fabrique.

  • Notre "méthode de fabrication" est maintenant abstraite dans Pizzeria.

  • Et nous avons transféré notre objet fabrique à cette méthode.

3.3. Laisser les sous-classes décider

Warning
QUESTION

Dans le schéma suivant, placez les méthodes au bon endroit de façon à ce que les procédures soient respectées tout en ayant des pizzas à variantes "régionales".

Pizzeria3
Important
Solution
Pizzeria3 sol

Chaque sous-classe redéfinit la méthode creerPizza(), tandis que toutes les sous-classes utilisent la méthode commanderPizza() définie dans Pizzeria.

Note Nous pourrions faire de commanderPizza() une méthode finale.

Voici un exemple de Pizzeria concrète :

public class PizzeriaBrest extends Pizzeria {
	Pizza creerPizza(String item) {
	if (choix.equals("fromage")) {
		return new PizzaFromageStyleBrest();
		} else if (choix.equals("vegetarienne")) {
		return new PizzaVegetarienneStyleBrest();
		} else if (choix.equals("fruitsDeMer")) {
		return new PizzaFruitsDeMerStyleBrest();
		} else if (choix.equals("poivrons")) {
		return new PizzaPoivronsStyleBrest();
		} else
		return null;
	}
}

3.4. Déclarer une méthode de fabrique

Rien qu’en apportant une ou deux transformations à Pizzeria, nous sommes passés d’un objet gérant l’instanciation de nos classes concrètes à un ensemble de sous-classes qui assument maintenant cette responsabilité.

Warning
QUESTION

Quelle est la déclaration exacte de la méthode creerPizza() de la classe Pizzeria ?

Important
Solution
protected abstract Pizza creerPizza(String type);
  • protected abstract : Comme une méthode de fabrication est abstraite, on compte sur les sous-classes pour gérer la création des objets.

  • Pizza : Une méthode de fabrication retourne un Produit qu’on utilise généralement dans les méthodes définies dans la superclasse.

  • creerPizza: Une méthode de fabrique isole le client (le code de la superclasse, tel commanderPizza() : elle lui évite de devoir connaître la sorte de Produit concret qui est réellement créée.

3.5. Récapitulons

Warning
QUESTION

Donnez le diagramme de séquence d’une "commande de pizza au fromage de type Strasbourg".

Important
Solution
Pizzeria sec
Note Vous implémenterez les classes manquantes en TP.

4. Le patron Fabrique (simple)

Nous y sommes, vous venez de décortiquer le patron Fabrique Simple

Note
Design pattern : Fabrique (simple)

Fabrique (simple) définit une interface pour la création d’un objet, mais en laissant à des sous-classes le choix des classes à instancier (voir aussi Fabrique abstraite).

fabrique
Figure 1. Modèle UML du patron Fabrique
google fabrique
Figure 2. Quelques exemples de description du patron Fabrique

Pour aller plus loin

Problème du main de test du jeu d’aventure

Vous avez sûrement dans votre main de l’application de jeu d’aventure une partie du code ressamblant à ceci :

Adaptation des comportements à la situation
if (choix == "Epee") {
	perso.setArme(new ComportementEpee());
}
else if (choix == "Arc") {
	perso.setArme(new ComportementArc());
	else if ...
	...
}

Ce code est peu adaptatif et va soufrir des évolutions, par exemple :

  • changement de la liste des armes possibles

  • rajouter des if then else à chaque nouvelle arme

  • suppression de certaines armes

  • …​

Warning
QUESTION(s)
  1. Isoler ce code dans une classe SimpleFabriqueArme qui possèdera une méthode creerComportementArme(String type) qui retourne le comportement adapté en fonction du paramètre reçu.

  2. Donnez le diagramme de classe UML™ de la nouvelle organisation.

  3. Donnez le diagramme de séquence du main.

Important
Solution
  1. Implémentation

    Extrait de FabriqueArme.java
    public class SimpleFabriqueArme {
    	public ComportementArme creerComportementArme(String type) {
    		ComportementArme compAdequat = null;
    		if (type.equals("Epee")) {
    			compAdequat = new ComportementEpee();
    		}
    		else if (type.equals("Arc")) {
    			compAdequat = new ComportementArc();
    		}
    		else compAdequat = new ComportementArmeless();
    		return compAdequat;
    	}
    }
  2. Diagramme de classe de la fabrique de comportements d’armes

    fabriqueArme
  3. Diagramme de séquence du main. Par exemple avec le code de test suivant :

    Chevalier perso = new Chevalier("JMI");
    
    SimpleFabriqueArme fabrique = new SimpleFabriqueArme();
    
    perso.setArme(fabrique.creerComportementArme("Epee"));
    perso.frapper();
    fabriqueArme seq

Vous complèterez le code du jeu d’aventure en TP. Mais avant cela étudions le patron Factory sur une exemple complet.

About…​

Document réalisé par Dut/Info-S3/M3105 via Asciidoctor (version 1.5.1) de 'Dan Allen', lui même basé sur AsciiDoc. Pour l’instant ce document est libre d’utilisation et géré par la 'Licence Creative Commons'. Licence Creative Commons licence Creative Commons Paternité - Partage à l'Identique 3.0 non transposé.